/*
 * Spring 2012 TCSS360 Deliverable 3
 * Otis Alexander
 */

package model;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import exceptions.AuctionLimitReachedException;
import exceptions.BadDateException;
import exceptions.BadTimeException;
import exceptions.SameNpoInYearException;

public class CalendarModel {

	private static final int MAX_AUCTIONS = 25;

	private static final int MAX_AUCTIONS_WEEK = 5;

	private static final long THREE_MONTHS_IN_MILLI =
			0x1CF7C5800L;

	private static final long YEAR_IN_MILLI = 0x75CD78800L;

	public Calendar calendar;

	public Map<Date, List<Auction>> pastAuctions;

	public Map<Date, List<Auction>> currentAuctions;

	public int auctionID;

	public CalendarModel() {
		super();
		calendar = new GregorianCalendar();
		pastAuctions = new HashMap<Date, List<Auction>>();
		currentAuctions =
				new HashMap<Date, List<Auction>>();
		auctionID = 0;
	}

	/**
	 * Adds an auction to one of the calendar's HashMaps
	 * depending on the date.
	 * 
	 * @pre auction_name != null
	 * @pre npo_name != null
	 * @pre auction_date != null
	 * @pre start_time < end_time pre num_of_items >= 0
	 */
	public void addAuction(final String auction_name,
			final String npo_name, final String npo_phone,
			final Date auction_date,
			final double start_time, final double end_time,
			final int num_of_items, final String comments)
			throws BadDateException, BadTimeException,
			SameNpoInYearException,
			AuctionLimitReachedException {

		checkAddAuctionBusRules(auction_date, npo_name,
				start_time, end_time);

		List<Auction> tempList;
		boolean present =
				auction_date.getTime() > System
						.currentTimeMillis();
		Map<Date, List<Auction>> auctions;

		if (present) {
			auctions = currentAuctions;
		} else {
			auctions = pastAuctions;
		}

		if (auctions.containsKey(auction_date)) {
			tempList = auctions.get(auction_date);
			tempList.add(new Auction(npo_name, npo_phone,
					auction_date, start_time, end_time,
					num_of_items, comments, auctionID + 1));
			auctionID++;
		} else {
			tempList = new ArrayList<Auction>();
			tempList.add(new Auction(npo_name, npo_phone,
					auction_date, start_time, end_time,
					num_of_items, comments, auctionID + 1));
			auctions.put(auction_date, tempList);
			auctionID++;
		}
	}

	/**
	 * Checks to see if the auction being added passes all
	 * of the appropriate business rules.
	 * 
	 * @pre auction_date != null
	 * @pre npo_name != null
	 * @pre start_time < end_time
	 */
	private void checkAddAuctionBusRules(
			final Date auction_date, final String npo_name,
			final double start_time, final double end_time)
			throws BadDateException, BadTimeException,
			SameNpoInYearException,
			AuctionLimitReachedException {

		calendar.setTimeInMillis(System.currentTimeMillis());

		Iterator<Entry<Date, List<Auction>>> list_it_current =
				currentAuctions.entrySet().iterator();

		Iterator<Entry<Date, List<Auction>>> list_it_past =
				pastAuctions.entrySet().iterator();

		int auctions = 0;

		// No more than one auction per year per Non-profit
		// organization can be
		// scheduled.

		// Move past auctions into past auctions HashMap.

		moveAuctions();

		while (list_it_current.hasNext()) {
			Entry<Date, List<Auction>> get_auctions_it_current =
					list_it_current.next();

			for (int i = 0; i < get_auctions_it_current
					.getValue().size(); i++) {
				if (get_auctions_it_current.getKey()
						.getTime() > calendar
						.getTimeInMillis()) {
					if (get_auctions_it_current.getValue()
							.get(i).getNPO() == npo_name) {
						throw new SameNpoInYearException();
					}
				}
			}
			auctions =
					auctions
							+ get_auctions_it_current
									.getValue().size();
		}

		while (list_it_past.hasNext()) {
			Entry<Date, List<Auction>> get_auctions_it_past =
					list_it_past.next();

			for (int i = 0; i < get_auctions_it_past
					.getValue().size(); i++) {

				if (get_auctions_it_past.getValue().get(i)
						.getNPO().equals(npo_name)) {
					if ((auction_date.getTime() - get_auctions_it_past
							.getKey().getTime()) < YEAR_IN_MILLI) {
						throw new SameNpoInYearException();
					}
				}
			}
		}

		// No more than 25 auctions may be schedule into the
		// future.

		if (auctions >= MAX_AUCTIONS) {
			throw new AuctionLimitReachedException(
					"There are already "
							+ currentAuctions.size()
							+ " scheduled.  No more future auctions can be scheduled.",
					"Auction limit reached.");
		}

		// An auction may not be scheduled more than 3
		// months from the current
		// date.

		if (auction_date.getTime() > calendar
				.getTimeInMillis() + THREE_MONTHS_IN_MILLI) {
			throw new BadDateException(
					"Auction date is more than 3 months from current date.",
					"Bad Date");
		}

		// No more than 2 auctions can be scheduled on the
		// same day, and the
		// start time of the second can be no earlier than 2
		// hours after the
		// end time of the first.

		if (currentAuctions.containsKey(auction_date)) {
			if (currentAuctions.get(auction_date).size() >= 2) {
				throw new AuctionLimitReachedException();
			} else if (currentAuctions.get(auction_date)
					.get(0).getStartTime() > end_time
					&& currentAuctions.get(auction_date)
							.get(0).getStartTime() - 2 < end_time) {
				throw new BadTimeException(
						"There is an auction starting at "
								+ currentAuctions
										.get(auction_date)
										.get(0)
										.getStartTime()
								+ ".  Auctions must be 2 hours apart",
						"Bad Time");
			} else if (currentAuctions.get(auction_date)
					.get(0).getEndTime() < start_time
					&& currentAuctions.get(auction_date)
							.get(0).getEndTime() + 2 > start_time) {
				throw new BadTimeException(
						"There is an auction ending at "
								+ currentAuctions
										.get(auction_date)
										.get(0)
										.getEndTime()
								+ ".  Auctions must be 2 hours apart",
						"Bad Time");
			} else if (currentAuctions.get(auction_date)
					.get(0).getStartTime() == start_time
					|| currentAuctions.get(auction_date)
							.get(0).getEndTime() == end_time) {
				throw new BadTimeException(
						"There is an auction starting at "
								+ currentAuctions
										.get(auction_date)
										.get(0)
										.getEndTime()
								+ " and ending at "
								+ currentAuctions
										.get(auction_date)
										.get(0)
										.getEndTime()
								+ ".  Auctions must be 2 hours apart",
						"Bad Time");
			}
		}

		// No more than 5 auctions may be scheduled for any
		// 7 day period.

		Calendar the_calendar = new GregorianCalendar();
		the_calendar.setTime(auction_date);
		int auctionCount = 1;
		for (int i = 0; i < 3; i++) {
			the_calendar.add(Calendar.DAY_OF_YEAR, -1);
			if (currentAuctions.containsKey(the_calendar
					.getTime())) {
				auctionCount =
						auctionCount
								+ currentAuctions.get(
										the_calendar
												.getTime())
										.size();
			}
		}
		for (int i = 0; i < 3; i++) {
			the_calendar.add(Calendar.DAY_OF_YEAR, 1);
			if (currentAuctions.containsKey(the_calendar
					.getTime())) {
				auctionCount =
						auctionCount
								+ currentAuctions.get(
										the_calendar
												.getTime())
										.size();
			}
		}
		if (auctionCount >= MAX_AUCTIONS_WEEK) {
			throw new AuctionLimitReachedException(
					Integer.toString(auctionCount), "");
		}
		auctionCount = 1;

		moveAuctions();
	}

	public List<Auction> getAuctions(final Date the_date) {
		List<Auction> the_list = null;
		if (currentAuctions.containsKey(the_date)) {
			the_list = currentAuctions.get(the_date);
		} else if (pastAuctions.containsKey(the_date)) {
			the_list = pastAuctions.get(the_date);
		}
		return the_list;
	}

	public Map<Date, List<Auction>> getAllPastAuctions() {
		return pastAuctions;
	}

	public Map<Date, List<Auction>> getAllCurrentAuctions() {
		return currentAuctions;
	}

	/**
	 * Generates an auction name based off of its NPO and
	 * its date.
	 * 
	 * @pre npo_name != null
	 * @pre the_date != null
	 * @post String = npo_name + the_date
	 */
	public String generateAuctionName(
			final String npo_name, final Date the_date) {
		Calendar c = new GregorianCalendar();
		c.setTime(the_date);
		return npo_name
				+ "-"
				+ c.getDisplayName(Calendar.MONTH, 2,
						Locale.ENGLISH) + "-"
				+ c.get(Calendar.DAY_OF_MONTH) + "-"
				+ c.get(Calendar.YEAR);
	}

	public Auction getAuction(
			final String the_auction_name,
			final Date the_date) {
		boolean present =
				the_date.getTime() > System
						.currentTimeMillis();

		Map<Date, List<Auction>> auctions;

		if (present) {
			auctions = currentAuctions;
		} else {
			auctions = pastAuctions;
		}
		if (auctions.containsKey(the_date)) {
			List<Auction> list = auctions.get(the_date);

			for (int i = 0; i < list.size(); i++) {
				if (list.get(i).getName()
						.compareTo(the_auction_name) == 0) {
					return list.get(i);
				}
			}
		}
		return null;
	}

	public Item getItem(final String uniqueID) {
		Item item = null;
		Iterator<Entry<Date, List<Auction>>> list_it =
				currentAuctions.entrySet().iterator();

		while (list_it.hasNext()) {
			Entry<Date, List<Auction>> get_auctions_it =
					list_it.next();
			Auction the_auction;
			List<Item> the_list;
			for (int i = 0; i < get_auctions_it.getValue()
					.size(); i++) {
				the_auction =
						get_auctions_it.getValue().get(i);
				the_list = the_auction.getItems();
				for (int j = 0; j < the_list.size(); j++) {
					if (the_list.get(j).get_unique_id()
							.compareTo(uniqueID) == 0) {
						item = the_list.get(j);
						return item;
					}
				}
			}
		}
		return item;
	}

	public void moveAuctions() {
		Iterator<Entry<Date, List<Auction>>> list_it_current =
				currentAuctions.entrySet().iterator();

		// Move past auctions into past auctions HashMap.

		while (list_it_current.hasNext()) {
			Entry<Date, List<Auction>> get_auctions_it =
					list_it_current.next();

			for (int i = 0; i < get_auctions_it.getValue()
					.size(); i++) {
				if (get_auctions_it.getKey().getTime() < System
						.currentTimeMillis()) {
					pastAuctions.put(
							get_auctions_it.getKey(),
							get_auctions_it.getValue());
					currentAuctions.remove(get_auctions_it
							.getKey());
				}
			}
		}
	}

	public Date convertDate(final String the_date) {
		SimpleDateFormat format =
				new SimpleDateFormat("MM/dd/yyyy");
		Date date = null;

		try {
			date = format.parse(the_date);
		} catch (ParseException e) {
			System.out.println("Bad date format");
		}

		return date;
	}
}
